package service;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.fasterxml.jackson.databind.JsonNode;
import com.xx.utils.JsonUtils;
import dao.SecureInvokeRecordDao;
import domain.dto.SecureInvokeDTO;
import domain.entity.SecureInvokeRecord;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import java.lang.reflect.Method;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.stream.Collectors;

@Slf4j
@AllArgsConstructor
public class SecureInvokeService {
    public static final double RETRY_INTERVAL_MINUTES = 2D;

    private final SecureInvokeRecordDao secureInvokeRecordDao;

    private final Executor executor;

    /**
     * 定时重试
     */
    @Scheduled(cron = "*/5 * * * * ?")
    public void retry() {
        List<SecureInvokeRecord> secureInvokeRecords = secureInvokeRecordDao.getWaitRetryRecords();
        for (SecureInvokeRecord secureInvokeRecord : secureInvokeRecords) {
            doAsyncInvoke(secureInvokeRecord);
        }
    }

    /**
     * 调用方法
     * @param record 记录参数
     * @param async 同步异步
     */
    public void invoke(SecureInvokeRecord record, boolean async) {
        //兜底策略
        boolean inTransaction = TransactionSynchronizationManager.isActualTransactionActive();
        //非事务状态，直接执行，不做任何保证。
        if (!inTransaction) {
            return;
        }
        //保存执行数据  ----  本地消息表存储调用记录
        save(record);
        //TransactionSynchronizationManager.registerSynchronization
        // 是 Spring Framework 中用于注册事务同步器（TransactionSynchronization）的方法。
        // 事务同步器在事务的不同阶段（比如提交前、提交后、完成后等）执行一些操作，
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
            //事务提交后执行
            @SneakyThrows
            @Override
            public void afterCommit() {
                //事务后执行
                if (async) {
                    doAsyncInvoke(record);
                } else {
                    doInvoke(record);
                }
            }
        });
    }

    /**
     * 同步执行调用方法 --- 核心调用方法
     * @param record 记录参数
     */
    private void doInvoke(SecureInvokeRecord record) {
        SecureInvokeDTO secureInvokeDTO = record.getSecureInvokeDTO();
        try {
            //存储事务状态值 --- 避免同步状态下事务判断失效导致代码错误
            SecureInvokeHolder.setInvoking();
            //获取类
            Class<?> beanClass = Class.forName(secureInvokeDTO.getClassName());
            //获取bean
            Object bean = SpringUtil.getBean(beanClass);
            //获取参数类型
            List<String> parameterStrings = JsonUtils.toList(secureInvokeDTO.getParameterTypes(), String.class);
            List<Class<?>> parameterClasses = getParameters(parameterStrings);
            //获取方法
            Method method = ReflectUtil.getMethod(beanClass, secureInvokeDTO.getMethodName(), parameterClasses.toArray(new Class[]{}));
            //获取值
            Object[] args = getArgs(secureInvokeDTO, parameterClasses);
            //执行方法
            method.invoke(bean, args);
            //执行成功更新状态
            removeRecord(record.getId());
        } catch (Throwable e) {
            log.error("SecureInvokeService invoke fail", e);
            //执行失败，等待下次执行
            retryRecord(record, e.getMessage());
        } finally {
            //移除事务状态
            SecureInvokeHolder.invoked();
        }
    }

    /**
     * 执行失败，尝试重试，记录信息
     * @param record 记录参数
     * @param errorMsg 错误信息
     */
    private void retryRecord(SecureInvokeRecord record, String errorMsg) {
        //重试次数+1
        Integer retryTimes = record.getRetryTimes() + 1;
        SecureInvokeRecord update = new SecureInvokeRecord();
        update.setId(record.getId());
        update.setFailReason(errorMsg);
        //更新下次重试时间
        update.setNextRetryTime(getNextRetryTime(retryTimes));
        //重试次数大于最大重试次数 ---> 默认失败
        if (retryTimes > record.getMaxRetryTimes()) {
            update.setStatus(SecureInvokeRecord.STATUS_FAIL);
        } else {
            update.setRetryTimes(retryTimes);
        }
        secureInvokeRecordDao.updateById(update);
    }

    /**
     * 获取下一次重试时间
     * @param retryTimes 尝试次数
     * @return Date
     */
    private Date getNextRetryTime(Integer retryTimes) {
        //指数递增算法 重试时间指数上升 2m 4m 8m 16m
        double waitMinutes = Math.pow(RETRY_INTERVAL_MINUTES, retryTimes);
        return DateUtil.offsetMinute(new Date(), (int) waitMinutes);
    }

    /**
     * 移除本地消息表里的数据
     * @param id id
     */
    private void removeRecord(Long id) {
        secureInvokeRecordDao.removeById(id);
    }

    /**
     * 通过参数类型获取数据值
     * @param secureInvokeDTO 安全调用DTO
     * @param parameterClasses 参数类型集合
     * @return Object[]
     */
    private Object[] getArgs(SecureInvokeDTO secureInvokeDTO, List<Class<?>> parameterClasses) {
        JsonNode jsonNode = JsonUtils.toJsonNode(secureInvokeDTO.getArgs());
        Object[] args = new Object[jsonNode.size()];
        for (int i = 0; i < jsonNode.size(); i++) {
            Class<?> aClass = parameterClasses.get(i);
            args[i] = JsonUtils.nodeToValue(jsonNode.get(i), aClass);
        }
        return args;
    }

    /**
     * 获取参数类型
     * @param parameterStrings 参数类型String集合
     * @return
     */
    private List<Class<?>> getParameters(List<String> parameterStrings) {
        return parameterStrings.stream().map(name -> {
            try {
                return Class.forName(name);
            } catch (ClassNotFoundException e) {
                log.error("SecureInvokeService class not fund", e);
            }
            return null;
        }).collect(Collectors.toList());
    }

    /**
     * 异步执行调用方法
     * @param record 记录参数
     */
    private void doAsyncInvoke(SecureInvokeRecord record) {
        executor.execute(() -> {
            System.out.println(Thread.currentThread().getName());
            doInvoke(record);
        });
    }

    /**
     * 保存调用记录
     * @param record 记录参数
     */
    private void save(SecureInvokeRecord record) {
        secureInvokeRecordDao.save(record);
    }


}
